package drds.plus.sql_process.utils.range;

import drds.plus.sql_process.type.Type;

/**
 * 这个包下面的类型不用进行注释清理(by czh)
 */
public class Range {
    private final Type type;
    private final Comparable minValue, maxValue;
    private Class elementClass;
    // The the minimum/maximum value is included in the range
    private boolean isMinIncluded = true, isMaxIncluded = true;

    public Range(Class elementClassNoUsed, Type type, Comparable minValue, Comparable maxValue) {
        // If both minValue and maxValue are null, check whether elementClass is an
        // instanceof Comparable.
        if ((minValue == null) && (maxValue == null)) {
            Class aClass = null;
            try {
                aClass = Class.forName("java.lang.Comparable");
            } catch (ClassNotFoundException e) {
            }
            this.elementClass = aClass;
        } else {
            this.elementClass = minValue == null ? maxValue.getClass() : minValue.getClass();// 选择其一
        }
        //
        if (minValue != null && minValue.getClass() != this.elementClass) {
            throw new IllegalArgumentException(("Range1"));
        }
        if (maxValue != null && maxValue.getClass() != this.elementClass) {
            throw new IllegalArgumentException(("Range2"));
        }
        this.type = type;
        this.minValue = minValue;
        this.maxValue = maxValue;
    }

    public Range(Class elementClass, Type type, Comparable minValue, boolean isMinIncluded, Comparable maxValue, boolean isMaxIncluded) {
        this(elementClass, type, minValue, maxValue);
        this.isMinIncluded = isMinIncluded;
        this.isMaxIncluded = isMaxIncluded;
    }

    public Class getElementClass() {
        return elementClass;
    }

    /**
     * Returns the minimum value of this <code>Range</code>. Returns null if the
     * <code>Range</code> is unbounded at this end.
     */
    public Comparable getMinValue() {
        return minValue;
    }

    /**
     * Returns the maximum value of this <code>Range</code>. Returns null if the
     * <code>Range</code> is unbounded at this end.
     */
    public Comparable getMaxValue() {
        return maxValue;
    }

    /**
     * Returns true if the minimum value is included within this <code>Range</code>.
     * If the range is unbounded at this end, this method will return true.
     */
    public boolean isMinIncluded() {
        if (this.minValue == null) {
            return true;
        }
        return isMinIncluded;
    }

    /**
     * Returns true if the maximum value is included within this <code>Range</code>.
     * If the range is unbounded at this end, this method will return true.
     */
    public boolean isMaxIncluded() {
        if (this.maxValue == null) {
            return true;
        }
        return isMaxIncluded;
    }
    ////////////////////////////////////////////////////////////////////////////////

    /**
     * Returns true if the specified value is within this <code>Range</code>, i.e.
     * is either equal to or greater than the minimum value of this
     * <code>Range</code> and is either lesser than or equal to the maximum value of
     * this <code>Range</code>.
     *
     * @param value The value to be checked for being within this
     *              <code>Range</code>.
     * @throws IllegalArgumentException if the <code>Class</code> of the value
     *                                  parameter is not the same setAliasAndSetNeedBuild the
     *                                  elementClass of this <code>Range</code>.
     */
    public boolean contains(Comparable value) {
        if (value != null && value.getClass() != elementClass) {
            throw new IllegalArgumentException(("Range3"));
        }
        // First check if the Range is empty
        if (isEmpty() == true)
            return false;
        // check both bounds.
        return isOverLowerBound(value) && isUnderUpperBound(value);
    }

    /**
     * Return true if the specific value is larger than the minimum of this range.
     * If this range is unbounded at the minimum end, return true; if the specific
     * value is null, return false;
     */
    private boolean isOverLowerBound(Comparable value) {
        // if the minimum side is unbounded, return true
        if (this.minValue == null) {
            return true;
        }
        // if the object passed in is null, return false: suppose it is
        // the "negative infinite". So be care when use this method.
        if (value == null) {
            return false;
        }
        if (isMinIncluded) {
            return type.compare(value, minValue) >= 0;
        }
        return type.compare(value, minValue) > 0;
    }

    /**
     * Return true if the specific value is smaller than the maximum of this range.
     * If this range is unbounded at the maximum end, return true; if the specific
     * value is null and the maximum end is bounded, return false, that is, suppose
     * this null is the "positive infinite".
     */
    private boolean isUnderUpperBound(Comparable value) {
        // if the maximum side is unbounded, return true
        if (this.maxValue == null) {
            return true;
        }
        // if the object passed in is null, return false: suppose it is
        // the "positive infinite". So be care when use this method.
        if (value == null) {
            return false;
        }
        if (isMaxIncluded) {
            return type.compare(value, maxValue) <= 0;
        }
        return type.compare(value, maxValue) > 0;
        // return maxValue.compareTo(value) > 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Returns true if the supplied <code>Range</code> is fully contained within
     * this <code>Range</code>. Fully contained is defined setAliasAndSetNeedBuild having the minimum
     * and maximum columnValueList of the fully contained range lie within the range
     * of columnValueList of the containing <code>Range</code>.
     *
     * @throws IllegalArgumentException if the <code>Class</code> of the elements of
     *                                  the given <code>Range</code> is not the same
     *                                  setAliasAndSetNeedBuild the <code>Class</code> of the elements
     *                                  of this <code>Range</code>.
     * @throws IllegalArgumentException if the given <code>Range</code> is null
     */
    public boolean contains(Range range) {
        if (range == null) {
            throw new IllegalArgumentException(("Range5"));
        }
        if (elementClass != range.getElementClass()) {
            throw new IllegalArgumentException(("Range4"));
        }
        if (range.isEmpty()) {
            return true;
        }
        Comparable minValue = range.getMinValue();
        Comparable maxValue = range.getMaxValue();
        boolean minSide, maxSide;
        if (minValue == null) {
            minSide = (this.minValue == null);
        } else {// minValue != null
            minSide = isOverLowerBound(minValue) || (isMinIncluded == range.isMinIncluded() && type.compare(minValue, this.minValue) == 0);
        }
        if (maxValue == null) {
            maxSide = (this.maxValue == null);
        } else {// maxValue != null
            // 区间判断+单值判断
            maxSide = isUnderUpperBound(maxValue) || (isMaxIncluded == range.isMaxIncluded() && type.compare(maxValue, this.maxValue) == 0);
        }
        return minSide && maxSide;
    }

    /**
     * Returns true if this <code>Range</code> intersects the given
     * <code>Range</code>.
     *
     * @throws IllegalArgumentException if the <code>Class</code> of the elements of
     *                                  the given <code>Range</code> is not the same
     *                                  setAliasAndSetNeedBuild the <code>Class</code> of the elements
     *                                  of this <code>Range</code>.
     * @throws IllegalArgumentException if the given <code>Range</code> is null
     */
    public boolean intersects(Range range) {
        if (range == null) {
            throw new IllegalArgumentException(("Range5"));
        }
        if (elementClass != range.getElementClass()) {
            throw new IllegalArgumentException(("Range4"));
        }
        return !intersect(range).isEmpty();
    }

    /**
     * Returns the intersection of this <code>Range</code> with the given
     * <code>Range</code>.
     *
     * @throws IllegalArgumentException if the <code>Class</code> of the elements of
     *                                  the given <code>Range</code> is not the same
     *                                  setAliasAndSetNeedBuild the <code>Class</code> of the elements
     *                                  of this <code>Range</code>.
     * @throws IllegalArgumentException if the given <code>Range</code> is null
     */
    public Range intersect(Range range) {

        if (range == null) {
            throw new IllegalArgumentException(("Range5"));
        }
        if (elementClass != range.getElementClass()) {
            throw new IllegalArgumentException(("Range4"));
        }
        if (this.isEmpty()) {
            Comparable value = this.minValue;
            if (value == null)
                value = this.maxValue;

            // get a non-null object to create an empty Range because the range is empty, so
            // temp will not be null
            return new Range(elementClass, type, value, false, value, false);
        }

        if (range.isEmpty()) {
            Comparable value = range.getMinValue();
            if (value == null)
                value = range.getMaxValue();

            // get a non-null object to create an empty Range because the range is empty, so
            // temp will not be null
            return new Range(elementClass, type, value, false, value, false);
        }

        boolean isNotOverLowerBound = !isOverLowerBound(range.getMinValue());// 小于等于下界
        boolean isNotUnderUpperBound = !isUnderUpperBound(range.getMaxValue());// 大于等于上界

        // If the minimum of this range is contained in the given range, the
        // minimum of the intersect range should be the one of this range;
        // similarly, we can get the maximum and the booleans.
        Comparable minValue = isNotOverLowerBound ? this.minValue : range.getMinValue();// (取大值)
        Comparable maxValue = isNotUnderUpperBound ? this.maxValue : range.getMaxValue();// (取小值)
        boolean isMinIncluded = isNotOverLowerBound ? this.isMinIncluded : range.isMinIncluded();// (取大值方向)
        boolean isMaxIncluded = isNotUnderUpperBound ? this.isMaxIncluded : range.isMaxIncluded();// (取小值方向)
        return new Range(elementClass, type, minValue, isMinIncluded, maxValue, isMaxIncluded);
    }

    /**
     * Returns the union of this <code>Range</code> with the given
     * <code>Range</code>. If this <code>Range</code> and the given
     * <code>Range</code> are disjoint, the <code>Range</code> returned setAliasAndSetNeedBuild a
     * result of the union will have a minimum value set to the minimum of the two
     * disjoint range's minimum columnValueList, and the maximum set to the maximum
     * of the two disjoint range's maximum columnValueList, thus including the
     * disjoint range within it.
     *
     * @throws IllegalArgumentException if the <code>Class</code> of the elements of
     *                                  the given <code>Range</code> is not the same
     *                                  setAliasAndSetNeedBuild the <code>Class</code> of the elements
     *                                  of this <code>Range</code>.
     * @throws IllegalArgumentException if the given <code>Range</code> is null
     */
    public Range union(Range range) {
        if (range == null) {
            throw new IllegalArgumentException(("Range5"));
        }
        if (elementClass != range.getElementClass()) {
            throw new IllegalArgumentException(("Range4"));
        }

        if (this.isEmpty()) {
            return new Range(elementClass, type, range.getMinValue(), range.isMinIncluded(), range.getMaxValue(), range.isMaxIncluded());
        }

        if (range.isEmpty()) {
            return new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded);
        }

        boolean isNotOverLowerBound = !isOverLowerBound(range.getMinValue());
        boolean isNotUnderUpperBound = !isUnderUpperBound(range.getMaxValue());
        // If the minimum of this range is contained in the given range, the
        // minimum of the union is the minimum of the given range; otherwise
        // it is the minimum of this range. So does the boolean isMinIncluded.
        // Similar for the maximum end
        // 和上面交集相反,取最大的范围
        Comparable minValue = isNotOverLowerBound ? range.getMinValue() : this.minValue;
        Comparable maxValue = isNotUnderUpperBound ? range.getMaxValue() : this.maxValue;
        boolean isMinIncluded = isNotOverLowerBound ? range.isMinIncluded() : this.isMinIncluded;
        boolean isMaxIncluded = isNotUnderUpperBound ? range.isMaxIncluded() : this.isMaxIncluded;
        return new Range(elementClass, type, minValue, isMinIncluded, maxValue, isMaxIncluded);
    }

    /**
     * Returns the <code>Range</code> of columnValueList that are in this
     * <code>Range</code> but not in the given <code>Range</code>. If the
     * subtraction results in two disjoint <code>Range</code>s, they will be
     * returned setAliasAndSetNeedBuild two elements of a <code>Range</code> array, otherwise the
     * resultant <code>Range</code> will be returned setAliasAndSetNeedBuild the first element of a
     * one element array. When this <code>Range</code> and the given
     * <code>Range</code> are both unbounded at both the ends (i.e both the
     * <code>Range</code>s are all inclusive), this method will return null setAliasAndSetNeedBuild
     * the first element of one element array, setAliasAndSetNeedBuild a result of the subtraction.
     * When this <code>Range</code> is completely contained in the given
     * <code>Range</code>, an empty <code>Range</code> is returned.
     *
     * @throws IllegalArgumentException if the <code>Class</code> of the elements of
     *                                  the given <code>Range</code> is not the same
     *                                  setAliasAndSetNeedBuild the <code>Class</code> of the elements
     *                                  of this <code>Range</code>.
     */
    public Range[] subtract(Range range) {
        if (range == null) {
            throw new IllegalArgumentException(("Range5"));
        }
        if (elementClass != range.getElementClass()) {
            throw new IllegalArgumentException(("Range4"));
        }
        // if this range is empty, return an empty range by copying this range;
        // if the given range is empty, return this range
        if (this.isEmpty() || range.isEmpty()) {
            Range[] ranges = {new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded)};
            return ranges;
        }
        Comparable minValue = range.getMinValue();
        Comparable maxValue = range.getMaxValue();
        boolean minIncluded = range.isMinIncluded();
        boolean maxIncluded = range.isMaxIncluded();
        // -%/+%减去-%/+%为null
        if (this.minValue == null && this.maxValue == null && minValue == null && maxValue == null) {
            Range[] ranges = {null};
            return ranges;
        }
        //
        // 差集只需要处理两边都包含,一边包含(2),不包含的情况
        boolean containsMinValue = this.contains(minValue);
        boolean containsMaxValue = this.contains(maxValue);
        // this range may be a full range [null, null]
        if (containsMinValue && containsMaxValue) {
            Range minRange = new Range(elementClass, type, this.minValue, this.isMinIncluded, minValue, !minIncluded);// 传入值大于minValue
            Range maxRange = new Range(elementClass, type, maxValue, !maxIncluded, this.maxValue, this.isMaxIncluded);// 传入值小于minValue。基于上述两点,则会分成两部分
            // if r1 is empty , return the second section only;
            // or if this range and the given range are all unbounded
            // at the minimum end, r1 is [null, null] but
            // should be empty. so we need to treat it setAliasAndSetNeedBuild a special case;
            // otherwise, a full range is returned
            if (minRange.isEmpty() || (this.minValue == null && minValue == null)) {
                Range[] ranges = {maxRange};// -%减去-%为null
                return ranges;
            }
            // similar to above
            if (maxRange.isEmpty() || (this.maxValue == null && maxValue == null)) {
                Range[] ranges = {minRange};// +%减去+%为null
                return ranges;
            }
            Range[] ranges = {minRange, maxRange};
            return ranges;
        }
        // [( this.minValue[不变] minValue[符号脱离改变]
        // if the min of the given range is in this range, return the range from the min
        // of this range to the min of the given range
        else if (containsMinValue) {
            Range[] ranges = {new Range(elementClass, type, this.minValue, this.isMinIncluded, minValue, !minIncluded)};// 需要去掉等值或者不等值
            return ranges;
        }
        // )] maxValue[符号脱离改变] this.maxValue[不变]
        // if the max of the given range is in this range, return the range from max of
        // given range to the max of this range
        else if (containsMaxValue) {
            Range[] ranges = {new Range(elementClass, type, maxValue, !maxIncluded, this.maxValue, this.isMaxIncluded)};// 需要去掉等值或者不等值
            return ranges;
        }

        // no overlap, just copy this range
        if ((minValue != null && !isUnderUpperBound(minValue)) || (maxValue != null && !isOverLowerBound(maxValue))) {
            Range[] ranges = {new Range(elementClass, type, this.minValue, this.isMinIncluded, this.maxValue, this.isMaxIncluded)};
            return ranges;
        }

        // this range is contained in the given range, return an empty range
        minValue = (this.minValue == null) ? this.maxValue : this.minValue;
        Range[] ranges = {new Range(elementClass, type, minValue, false, minValue, false)};
        return ranges;
    }

    public int hashCode() {

        int code = this.elementClass.hashCode();

        if (isEmpty())
            return code;

        code ^= Integer.MAX_VALUE;

        if (this.minValue != null) {
            code ^= this.minValue.hashCode();
            if (this.isMinIncluded)
                code ^= 0xFFFF0000;
        }

        if (this.maxValue != null) {
            code ^= this.maxValue.hashCode() * 31;
            if (this.isMaxIncluded)
                code ^= 0xFFFF;
        }

        return code;
    }

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (!(object instanceof Range)) {
            return false;
        }
        Range range = (Range) object;
        // if the element class is not same, return false
        if (this.elementClass != range.getElementClass()) {
            return false;
        }
        // two empty ranges are equal
        if (this.isEmpty() && range.isEmpty()) {
            return true;
        }
        //////////////////////////////////////////////////////////////////////
        Comparable minValue = range.getMinValue();
        // if the minimum is not null, compare both minValue and the boolean
        if (this.minValue != null) {
            if (!(this.type.compare(this.minValue, minValue) == 0)) {
                return false;
            }
            // if (!this.minValue.equals(min)) return false;
            if (this.isMinIncluded != range.isMinIncluded()) {
                return false;
            } else {
                // 需要比较max...
            }
        } else {// this.minValue==null
            if (minValue != null) {// if the minimum is unbounded, just check the given range is bounded or not
                return false;
            } else {
                // 需要比较max...
            }
        }
        // 比较max...
        Comparable max = range.getMaxValue();
        // if the maximum is not null, compare both maxValue and the boolean
        if (this.maxValue != null) {
            if (!(this.type.compare(maxValue, max) == 0)) {
                return false;
            }
            // if (!this.maxValue.equals(max)) return false;
            return this.isMaxIncluded == range.isMaxIncluded();
        } else {// this.maxValue == null
            // if the maximum is unbounded, just check the given range is bounded or not
            return max == null;
        }
    }

    /**
     * Returns true if this <code>Range</code> is empty, i.e. if the minimum value
     * is greater than the maximum value, if both are included, or if the minimum
     * value is greater than equal to the maximum value if either the minimum or
     * maximum value is excluded.
     */
    public boolean isEmpty() {
        // an unbounded range is not empty
        if (minValue == null || maxValue == null) {
            return false;
        }
        int result = this.type.compare(minValue, maxValue);
        // if the minimum is larger than the maximum, this range is empty
        if (result > 0) {
            return true;
        }
        // if the minimum equals to the maximum and one side is not inclusive, then it
        // is empty
        if (result == 0) {
            return !(isMinIncluded & isMaxIncluded);
        }
        // otherwise, not empty
        return false;
    }

    public boolean isSingleValue() {
        return this.maxValue != null && this.minValue != null//
                && this.isMaxIncluded && this.isMinIncluded//
                && this.type.compare(maxValue, minValue) == 0;//
    }

    public String toString() {
        // if inclusive, display '[' otherwise display '('
        char start = isMinIncluded ? '[' : '(';
        char end = isMaxIncluded ? ']' : ')';
        // if both ends are bounded
        if (minValue != null && maxValue != null) {
            return start + this.minValue.toString() + ", " + this.maxValue.toString() + end;
        }
        // if the maximum end is bounded
        if (maxValue != null) {
            return start + "-∞, " + this.maxValue.toString() + end;
        }
        // if the minimum end is bounded
        if (minValue != null) {
            return start + this.minValue.toString() + ", " + "+∞" + end;
        }
        // both ends are unbounded
        return start + "-∞, +∞" + end;
    }
}
